/*
Copyright (C) 2008 raohmaru <http://www.raohmaru.com>

This file is part of Raohmaru AS3 Framework.

Raohmaru AS3 Framework is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Raohmaru AS3 Framework is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Raohmaru AS3 Framework.  If not, see <http://www.gnu.org/licenses/>.
*/

package jp.raohmaru.media.filters
{
import flash.media.Sound;
import flash.utils.ByteArray;

/**
 * La clase PitchFilter le permite aplicar un efecto de cambio de tono (frecuencia) a objetos de sonido SoundFX.
 * @author Raohmaru
 * @version 1.0
 * @source Original code by Andre Michelle <http://blog.andre-michelle.com/2009/pitch-mp3/>
 */
public class PitchFilter implements IFilter
{
	private var _target: ByteArray,	
				_position: Number,
				_rate: Number,
				_blockSize: int,
				_scaledBlockSize: Number;
	
	/**
	 * Define la frecuencia de muestreo, o el número de muestras por unidad de tiempo que se toman del objeto de sonido
	 * de origen. El valor mínimo es 0.
	 */
	public function get rate(): Number
	{
		return _rate;
	}	
	public function set rate( value: Number ): void
	{
		if( value < 0.0 )
			value = 0;
		
		_rate = value;
		_scaledBlockSize = _blockSize * _rate;
	}
	
	/**
	 * Define la cantidad de muestras de sonido a extraer y escribir en el flujo dinámico de audio.
	 * Proporcione entre 2048 y 8192 muestras. Para obtener el mejor rendimiento, proporcione tantas muestras
	 * como sea posible. Cuanto menor sea el número de muestras, más probable será la aparición de interferencias
	 * en la reproducción.
	 */
	public function get blockSize(): int
	{
		return _blockSize;
	}	
	public function set blockSize( value: int ): void
	{		
		if		(value < 2048) value = 2048;
		else if (value > 8192) value = 8192;
		
		_blockSize = value;
		_scaledBlockSize = _blockSize * _rate;
	}
	
	
	/**
	 * Nuevo objeto PitchFilter que puede añadirse a lista de filtros de un objeto SoundFX.
	 * @param rate Frecuencia de muestreo.
	 * @param blockSize Cantidad de muestras de sonido a procesar.
	 */
	public function PitchFilter( rate :Number=1.0, blockSize :int=2048 )
	{
		_target = new ByteArray();		
		_position = 0.0;
		this.blockSize = blockSize;
		this.rate = rate;
	}
	
	/**
	 * @inheritDoc
	 */
	public function apply( source :Sound, sample :ByteArray ): int
	{
		//-- REUSE INSTEAD OF RECREATION
		_target.position = 0;
				
		var positionInt: int = _position;
		var alpha: Number = _position - positionInt;
		
		var positionTargetNum: Number = alpha;
		var positionTargetInt: int = -1;
		
		//-- COMPUTE NUMBER OF SAMPLES NEED TO PROCESS BLOCK (+2 FOR INTERPOLATION)
		var need: int = int( _scaledBlockSize+1 ) + 2;
		
		//-- EXTRACT SAMPLES
		var read: int = source.extract( _target, need, positionInt );
		
		var n: int = (read == need) ? _blockSize : read / _rate;		
		var i :int = n;
		
		var l0: Number;
		var r0: Number;
		var l1: Number;
		var r1: Number;
		
		while(i > 0)
		{
			//-- AVOID READING EQUAL SAMPLES, IF RATE < 1.0
			if( int( positionTargetNum ) != positionTargetInt )
			{
				positionTargetInt = positionTargetNum;
				
				//-- SET TARGET READ POSITION
				_target.position = positionTargetInt << 3;
				
				//-- READ TWO STEREO SAMPLES FOR LINEAR INTERPOLATION
				l0 = _target.readFloat();
				r0 = _target.readFloat();
				
				l1 = _target.readFloat();
				r1 = _target.readFloat();
			}
			
			//-- WRITE INTERPOLATED AMPLITUDES INTO STREAM
			sample.writeFloat( l0 + alpha * ( l1 - l0 ) );
			sample.writeFloat( r0 + alpha * ( r1 - r0 ) );
			
			//-- INCREASE TARGET POSITION
			positionTargetNum += _rate;
			
			//-- INCREASE FRACTION AND CLAMP BETWEEN 0 AND 1
			alpha += _rate;
			while( alpha >= 1.0 ) --alpha;
			
			--i;
		}
		
		//-- FILL REST OF STREAM WITH ZEROs
		if( n < _blockSize )
		{
			while( n < _blockSize )
			{
				sample.writeFloat( 0.0 );
				sample.writeFloat( 0.0 );
				
				++n;
			}
		}
		
		//-- INCREASE SOUND POSITION
		_position += _scaledBlockSize;
		
		return read;
	}
	
	/**
	 * @inheritDoc
	 */
	public function reset(position: Number=0.0) :void
	{
		_position = position;
	}
}
}